home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.1 (Developer) [x86] / NeXT Step 3.1 Intel dev.cdr.dmg / NextDeveloper / Examples / AppKit / Draw / DrawApp.m < prev    next >
Text File  |  1993-01-08  |  24KB  |  928 lines

  1. #import "draw.h"
  2.  
  3. const int DrawVersion = 48;    /* minor version of the program */
  4.  
  5. @implementation DrawApp : Application
  6. /*
  7.  * This class is used primarily to handle the opening of new documents
  8.  * and other application-wide activity (such as responding to messages from
  9.  * the tool palette).  It listens for requests from the Workspace Manager
  10.  * to open a draw-format file as well as target/action messages from the
  11.  * New and Open... menu items.  It also keeps the menus in sync by
  12.  * fielding the menu items' updateActions.
  13.  */
  14.  
  15. /* Private C functions used to implement methods in this class. */
  16.  
  17. static void initMenu(Menu *menu)
  18. /*
  19.  * Sets the updateAction for every menu item which sends to the
  20.  * First Responder (i.e. their target is nil).  When autoupdate is on,
  21.  * every event will be followed by an update of each of the menu items
  22.  * which is visible.  This keep all unavailable menu items dimmed out
  23.  * so that the user knows what options are available at any given time.
  24.  * Returns the activate menu if is found in this menu.
  25.  */ 
  26. {
  27.     int count;
  28.     Matrix *matrix;
  29.     MenuCell *cell;
  30.     id matrixTarget, cellTarget;
  31.  
  32.     matrix = [menu itemList];
  33.     matrixTarget = [matrix target];
  34.  
  35.     count = [matrix cellCount];
  36.     while (count--) {
  37.     cell = [matrix cellAt:count :0];
  38.     cellTarget = [cell target];
  39.     if (!matrixTarget && !cellTarget) {
  40.         [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
  41.     } else if ([cell hasSubmenu]) {
  42.         initMenu(cellTarget);
  43.     }
  44.     }
  45. }
  46.  
  47. static DrawDocument *documentInWindow(Window *window)
  48. /*
  49.  * Checks to see if the passed window's delegate is a DrawDocument.
  50.  * If it is, it returns that document, otherwise it returns nil.
  51.  */
  52. {
  53.     id document = [window delegate];
  54.     return [document isKindOf:[DrawDocument class]] ? document : nil;
  55. }
  56.  
  57. static Window *findDocument(const char *name)
  58. /*
  59.  * Searches the window list looking for a DrawDocument with the specified name.
  60.  * Returns the window containing the document if found.
  61.  * If name == NULL then the first document found is returned.
  62.  */
  63. {
  64.     int count;
  65.     DrawDocument *document;
  66.     Window *window;
  67.     List *windowList;
  68.  
  69.     windowList = [NXApp windowList];
  70.     count = [windowList count];
  71.     while (count--) {
  72.     window = [windowList objectAt:count];
  73.     document = documentInWindow(window);
  74.     if ([document isSameAs:name]) return window;
  75.     }
  76.  
  77.     return nil;
  78. }
  79.  
  80. static DrawDocument *openFile(const char *directory, const char *name, BOOL display)
  81. /*
  82.  * Opens a file with the given name in the specified directory.
  83.  * If we already have that file open, it is ordered front.
  84.  * Returns the document if successful, nil otherwise.
  85.  */
  86. {
  87.     Window *window;
  88.     char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];
  89.  
  90.     if (name && *name) {
  91.     if (!directory) {
  92.         directory = ".";
  93.     } else if (*directory != '/') {
  94.         strcpy(buffer, "./");
  95.         strcat(buffer, directory);
  96.         directory = buffer;
  97.     }
  98.     if (!chdir(directory) && getwd(path)) {
  99.         strcat(path, "/");
  100.         strcat(path, name);
  101.         window = findDocument(path);
  102.         if (window) {
  103.         if (display) [window makeKeyAndOrderFront:window];
  104.         return [window delegate];
  105.         } else {
  106.         return [DrawDocument newFromFile:path andDisplay:display];
  107.         }
  108.     } else {
  109.         NXRunLocalizedAlertPanel(NULL, "Open", "Invalid path: %s", NULL, NULL, NULL, directory, "Alert shown to user when he has tried to open up a file and has specified a file in a directory which is inaccessible.  The directory is either unreadable or does not exist for some reason.");
  110.     }
  111.     }
  112.  
  113.     return nil;
  114. }
  115.  
  116. static DrawDocument *openDocument(const char *document, BOOL display)
  117. {
  118.     char *directory, *name, *ext;
  119.     char buffer[MAXPATHLEN+1];
  120.  
  121.     strcpy(buffer, document);
  122.     ext = strrchr(buffer, '.');
  123.     if (!ext || strcmp(ext, ".draw")) strcat(buffer, ".draw");
  124.     name = strrchr(buffer, '/');
  125.     if (name) {
  126.     *name++ = '\0';
  127.     directory = buffer;
  128.     } else {
  129.     name = buffer;
  130.     directory = NULL;
  131.     }
  132.  
  133.     return openFile(directory, name, display);
  134. }
  135.  
  136. /* Public methods */
  137.  
  138. + initialize
  139. /*
  140.  * Initializes the defaults.
  141.  */
  142. {
  143.     const NXDefaultsVector DrawDefaults = {
  144.     { "KnobWidth", "5" },
  145.     { "KnobHeight", "5" },
  146.     { "KeyMotionDelta", "1"},
  147.     { "Quit", NULL },
  148.     { "RemoteControl", "YES" },
  149.     { "HideCursorOnMove", NULL },
  150.     { NULL, NULL }
  151.     };
  152.  
  153.     NXRegisterDefaults("Draw", DrawDefaults);
  154.  
  155.     return self;
  156. }
  157.  
  158. + new
  159. /*
  160.  * setAutoupdate:YES means that updateWindows will be called after
  161.  * every event is processed (this is how we keep our inspector and
  162.  * our menu items up to date).
  163.  */
  164. {
  165.     self = [super new];
  166.     [self setAutoupdate:YES];
  167.     return self;
  168. }
  169.  
  170. /* General application status and information querying/modifying methods. */
  171.  
  172. - currentGraphic
  173. /*
  174.  * The current factory to use to create new Graphics.
  175.  */
  176. {
  177.     return currentGraphic;
  178. }
  179.  
  180. - (DrawDocument *)currentDocument
  181. /*
  182.  * The DrawDocument in the main window (dark gray title bar).
  183.  */
  184. {
  185.     return documentInWindow(mainWindow);
  186. }
  187.  
  188. - (const char *)currentDirectory
  189. /*
  190.  * Directory where Draw is currently "working."
  191.  */
  192. {
  193.     const char *cdir = [[self currentDocument] directory];
  194.     return (cdir && *cdir) ? cdir : (haveOpenedDocument ? [[OpenPanel new] directory] : NXHomeDirectory());
  195. }
  196.  
  197. /*
  198.  * Call these to enter/exit the TextGraphic tool.
  199.  * These are used currently by the Undo architecture.
  200.  */
  201.  
  202. - startEditMode
  203. {
  204.     [tools selectCellAt:1 :0];
  205.     [tools sendAction];
  206.     return self;
  207. }
  208.  
  209. - endEditMode
  210. {
  211.     [tools selectCellAt:0 :0];
  212.     [tools sendAction];
  213.     return self;
  214. }
  215.  
  216. /* Application-wide shared panels */
  217.  
  218. static const char *cleanTitle(const char *menuItem, char *realTitle)
  219. /*
  220.  * Just strips off trailing "..."'s.
  221.  */
  222. {
  223.     int length = menuItem ? strlen(menuItem) : 0;
  224.  
  225.     if (length > 3 && !strcmp(menuItem+length-3, "...")) {
  226.     strcpy(realTitle, menuItem);
  227.     realTitle[length-3] = '\0';
  228.     return realTitle;
  229.     } else if (length > 1 && (menuItem[length-1] == '\274')) {
  230.     strcpy(realTitle, menuItem);
  231.     realTitle[length-1] = '\0';
  232.     return realTitle;
  233.     } else {
  234.     return menuItem;
  235.     }
  236. }
  237.  
  238. - (SavePanel *)saveToPanel:sender
  239. /*
  240.  * Returns a SavePanel with the accessory view which allows the user to
  241.  * pick which type of file she wants to save.  The title of the Panel is
  242.  * set to whatever was on the menu item that brought it up (it is assumed
  243.  * that sender is the menu that has the item in it that was chosen to
  244.  * cause the action which requires the SavePanel to come up).
  245.  */
  246. {
  247.     char title[100];
  248.     const char *theTitle;
  249.     SavePanel *savepanel = [SavePanel new];
  250.  
  251.     if (!savePanelAccessory) {
  252.     [self loadNibSection:"SavePanelAccessory.nib" owner:self withNames:NO fromZone:[savepanel zone]];
  253.     }
  254.     theTitle = cleanTitle([[sender selectedCell] title], title);
  255.     if (theTitle) [savepanel setTitle:theTitle];
  256.     [savepanel setAccessoryView:savePanelAccessory];
  257.     [spamatrix selectCellAt:0 :0];
  258.     [savepanel setRequiredFileType:"draw"];
  259.  
  260.     return savepanel;
  261. }
  262.  
  263. - (SavePanel *)saveAsPanel:sender
  264. /*
  265.  * Returns a regular SavePanel with "draw" as the required file type.
  266.  */
  267. {
  268.     char title[100];
  269.     const char *theTitle;
  270.     SavePanel *savepanel = [SavePanel new];
  271.  
  272.     [savepanel setAccessoryView:nil];
  273.     theTitle = cleanTitle([[sender selectedCell] title], title);
  274.     if (theTitle) [savepanel setTitle:theTitle];
  275.     [savepanel setRequiredFileType:"draw"];
  276.  
  277.     return savepanel;
  278. }
  279.  
  280. - (GridView *)gridInspector
  281. /*
  282.  * Returns the application-wide inspector for a document's grid.
  283.  * Note that if we haven't yet loaded the GridView panel, we do it.
  284.  * The instance variable gridInspector is set in setGridInspector:
  285.  * since it is set as an outlet of the owner (self, i.e. DrawApp).
  286.  */
  287. {
  288.     if (!gridInspector) {
  289.     NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
  290.     NXNameZone(zone, "GridView");
  291.     [self loadNibSection:"GridView.nib" owner:self withNames:NO fromZone:zone];
  292.     NXMergeZone(zone);
  293.     }
  294.     return gridInspector;
  295. }
  296.  
  297. - (Panel *)inspectorPanel
  298. /*
  299.  * Returns the application-wide inspector for Graphics.
  300.  */
  301. {
  302.     if (!inspectorPanel) {
  303.     NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
  304.     NXNameZone(zone, "Inspector");
  305.     [self loadNibSection:"InspectorPanel.nib" owner:self withNames:NO fromZone:zone];
  306.     [inspectorPanel setFrameAutosaveName:"Inspector"];
  307.     [inspectorPanel setBecomeKeyOnlyIfNeeded:YES];
  308.     [[inspectorPanel delegate] preset];
  309.     NXMergeZone(zone);
  310.     }
  311.     return inspectorPanel;
  312. }
  313.  
  314. - (DrawPageLayout *)pageLayout
  315. /*
  316.  * Returns the application-wide DrawPageLayout panel.
  317.  */
  318. {
  319.     static DrawPageLayout *dpl = nil;
  320.  
  321.     if (!dpl) {
  322.     dpl = [DrawPageLayout new];
  323.     [self loadNibSection:"PageLayoutAccessory.nib" owner:dpl withNames:NO fromZone:[dpl zone]];
  324.     }
  325.  
  326.     return dpl;
  327. }
  328.  
  329. - orderFrontInspectorPanel:sender
  330. /*
  331.  * Creates the inspector panel if it doesn't exist, then orders it front.
  332.  */
  333. {
  334.     [[self inspectorPanel] orderFront:self];
  335.     return self;
  336. }
  337.  
  338. /* Setting up the Fax Cover Sheet menu */
  339.  
  340. #define FAX_NOTE NXLocalString("Notes", NULL, "Fax Cover Sheet item which allows the user to type in any thing he/she wants.")
  341.  
  342. - setFaxCoverSheetMenu:anObject
  343. /*
  344.  * This goes through all the entries in CoverSheet.strings and makes
  345.  * an entry in the cover sheet menu for them.  This is kind of a kooky
  346.  * method, but it makes Draw much more usable as a Fax Cover Sheet
  347.  * editor.
  348.  */
  349. {
  350.     Menu *fcsMenu;
  351.     MenuCell *cell;
  352.     const char *key, *value;
  353.     NXStringTable *table;
  354.     char path[MAXPATHLEN+1];
  355.  
  356.     if (fcsMenu = [anObject target]) {
  357.     if ([[NXBundle mainBundle] getPath:path forResource:"CoverSheet" ofType:"strings"]) {
  358.         if (table = [[[NXStringTable allocFromZone:[self zone]] init] readFromFile:path]) {
  359.         NXHashState state = [table initState];
  360.         while ([table nextState:&state key:(void **)&key value:(void **)&value]) {
  361.             cell = [fcsMenu addItem:value action:@selector(addLocalizableCoverSheetEntry:) keyEquivalent:0];
  362.             [cell setAltTitle:key];
  363.         }
  364.         [table free];
  365.         }
  366.     }
  367.     [fcsMenu addItem:FAX_NOTE action:@selector(addCoverSheetEntry:) keyEquivalent:0];
  368.     }
  369.  
  370.     return self;
  371. }
  372.  
  373. /* Target/Action methods */
  374.  
  375. - info:sender
  376. /*
  377.  * Brings up the information panel.
  378.  */
  379. {
  380.     char buf[20];
  381.  
  382.     if (!infoPanel) {
  383.     NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
  384.     NXNameZone(zone, "InfoPanel");
  385.     [self loadNibSection:"InfoPanel.nib" owner:self withNames:NO fromZone:zone];
  386.     [infoPanel setFrameAutosaveName:"InfoPanel"];
  387.     sprintf(buf, "(v%2d)", DrawVersion);
  388.     [version setStringValue:buf];
  389.     NXMergeZone(zone);
  390.     }
  391.  
  392.     [infoPanel orderFront:self];
  393.  
  394.     return self;
  395. }
  396.  
  397. #define NO_HELP NXLocalString("Couldn't find any help!  Sorry ...", NULL, "Message given to the user when the help document could not be found.")
  398.  
  399. - help:sender
  400. /*
  401.  * Loads up the Help draw document.
  402.  * Note the use of NXBundle so that the Help document can be localized.
  403.  */
  404. {
  405.     DrawDocument *document = nil;
  406.     char path[MAXPATHLEN+1];
  407.  
  408.     if ([[NXBundle mainBundle] getPath:path forResource:"Help" ofType:"draw"]) {
  409.     if (document = openDocument(path, NO)) {
  410.         [document setTemporaryTitle:NXLocalString("Help", NULL, "The title of the help document.")];
  411.         [[[document view] window] makeKeyAndOrderFront:self];
  412.     }
  413.     }
  414.  
  415.     if (!document) NXRunAlertPanel(NULL, NO_HELP, NULL, NULL, NULL);
  416.  
  417.     return self;
  418. }
  419.  
  420. - new:sender
  421. /*
  422.  * Creates a new document--called by pressing New in the Document menu.
  423.  */
  424. {
  425.     [DrawDocument new];
  426.     return self;
  427. }
  428.  
  429. - open:sender
  430. /*
  431.  * Called by pressing Open... in the Window menu.
  432.  */
  433. {
  434.     const char *directory;
  435.     const char *const *files;
  436.     const char *const drawFileTypes[2] = { "draw", NULL };
  437.     OpenPanel *openpanel = [[OpenPanel new] allowMultipleFiles:YES];
  438.  
  439.     directory = [self currentDirectory];
  440.     if (directory && (*directory == '/')) [openpanel setDirectory:directory];
  441.     if ([openpanel runModalForTypes:drawFileTypes]) {
  442.     files = [openpanel filenames];
  443.     directory = [openpanel directory];
  444.     while (files && *files) {
  445.         if (*files) haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
  446.         files++;
  447.     }
  448.     }
  449.  
  450.     return self;
  451. }
  452.  
  453. - saveAll:sender
  454. /*
  455.  * Saves all the documents.
  456.  */
  457. {
  458.     int count;
  459.     Window *window;
  460.  
  461.     count = [windowList count];
  462.     while (count--) {
  463.     window = [windowList objectAt:count];
  464.     [documentInWindow(window) save:sender];
  465.     }
  466.  
  467.     return nil;
  468. }
  469.  
  470. #define UNSAVED_DOCUMENTS NXLocalString("You have unsaved documents.", NULL, "Message given to user when he tries to quit the application without saving all of his documents.")
  471. #define REVIEW_UNSAVED NXLocalString("Review Unsaved", NULL, "Choice (on a button) given to user which allows him to review all his unsaved documents if he quits the application without saving them all first.")
  472. #define QUIT_ANYWAY NXLocalString("Quit Anyway", NULL, "Choice (on a button) given to user which allows him to quit the application even though he has not saved all of his documents first.")
  473. #define QUIT NXLocalString("Quit", NULL, "The operation of exiting the application.")
  474.  
  475. - terminate:sender cancellable:(BOOL)cancellable
  476. /*
  477.  * Makes sure all documents get an opportunity
  478.  * to be saved before exiting the program.  If we are terminating because
  479.  * the user logged out of the workspace (or powered off), then we cannot
  480.  * give the user the option of cancelling the quit (that's what the
  481.  * cancellable flag is for).
  482.  */
  483. {
  484.     int count, choice;
  485.     Window *window;
  486.     id document;
  487.  
  488.     count = [windowList count];
  489.     while (count--) {
  490.     window = [windowList objectAt:count];
  491.      document = [window delegate];
  492.     if ([document respondsTo:@selector(isDirty)] && [document isDirty]) {
  493.         if (cancellable) {
  494.         choice = NXRunAlertPanel(QUIT, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, CANCEL);
  495.         } else {
  496.         choice = NXRunAlertPanel(QUIT, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, NULL);
  497.         }
  498.         if (choice == NX_ALERTOTHER)  {
  499.         return self;
  500.         } else if (choice == NX_ALERTDEFAULT) {
  501.         count = [windowList count];
  502.         while (count--) {
  503.             window = [windowList objectAt:count];
  504.             document = [window delegate];
  505.             if ([document respondsTo:@selector(windowWillClose:cancellable:)]) {
  506.             if (![document windowWillClose:sender cancellable:cancellable] && cancellable) {
  507.                 return self;
  508.             } else {
  509.                 [document close];
  510.                 [window close];
  511.             }
  512.             }
  513.         }
  514.         }
  515.         break;
  516.     }
  517.     }
  518.  
  519.     return nil;
  520. }
  521.  
  522. - terminate:sender
  523. /*
  524.  * Overridden to give user the opportunity to save unsaved files.
  525.  */
  526. {
  527.     if (![self terminate:sender cancellable:YES]) {
  528.     return [super terminate:sender];
  529.     } else {
  530.     return self;
  531.     }
  532. }
  533.  
  534. /*
  535.  * Application object delegate methods.
  536.  * Since we don't have an application delegate, messages that would
  537.  * normally be sent there are sent to the Application object itself instead.
  538.  */
  539.  
  540. - appDidInit:(Application *)sender
  541. /*
  542.  * Makes the tool palette not ever become the key window.
  543.  * Check for files to open specified on the command line.
  544.  * Initialize the menus.
  545.  * If there are no open documents (and we are not being
  546.  * launched to service a Services request or otherwise
  547.  * being invoked due to interapplication communication),
  548.  * then open a blank one.
  549.  */
  550. {
  551.     int i;
  552.     Panel *toolWindow;
  553.  
  554.     toolWindow = [tools window];
  555.     [toolWindow setFrameAutosaveName:[toolWindow title]];
  556.     [toolWindow setBecomeKeyOnlyIfNeeded:YES];
  557.     [toolWindow setFloatingPanel:YES];
  558.     [toolWindow orderFront:self];
  559.  
  560.     if (NXArgc > 1) {
  561.     for (i = 1; i < NXArgc; i++) {
  562.         haveOpenedDocument = openDocument(NXArgv[i], YES) || haveOpenedDocument;
  563.     }
  564.     }
  565.  
  566.     if (!haveOpenedDocument && !NXGetDefaultValue([self appName], "NXServiceLaunch")) [self new:self];
  567.  
  568.     if (NXGetDefaultValue([self appName], "Quit")) {
  569.     [self activateSelf:YES];
  570.     NXPing();
  571.     [self terminate:nil];
  572.     }
  573.  
  574.     initMenu([NXApp mainMenu]);
  575.  
  576.     return self;
  577. }
  578.  
  579. - (int)app:sender openFile:(const char *)path type:(const char *)type
  580. /*
  581.  * This method is performed whenever a user double-clicks on an icon
  582.  * in the Workspace Manager representing a Draw program document.
  583.  */
  584. {
  585.     if (type && !strcmp(type, "draw")) {
  586.     if (openDocument(path, YES)) {
  587.         haveOpenedDocument = YES;
  588.         return YES;
  589.     }
  590.     }
  591.  
  592.     return NO;
  593. }
  594.  
  595. - (BOOL)appAcceptsAnotherFile:(Application *)sender
  596. /*
  597.  * We accept any number of appOpenFile:type: messages.
  598.  */
  599. {
  600.     return YES;
  601. }
  602.  
  603. - app:sender powerOffIn:(int)ms andSave:(int)andSave
  604. /*
  605.  * Give the user a chance to save his documents.
  606.  */
  607. {
  608.     if (andSave) [self terminate:nil cancellable:NO];
  609.     return self;
  610. }
  611.  
  612. /* Listener/Speaker remote methods */
  613.  
  614. /*
  615.  * Note that anybody can send these messages to a running version
  616.  * of Draw, so we protect the ones that can affect the current
  617.  * document by only allowing them to happen if the Draw default
  618.  * "RemoteControl" is set.
  619.  */
  620.  
  621. - (int)msgDirectory:(const char **)fullPath ok:(int *)flag
  622. {
  623.     *fullPath = [self currentDirectory];
  624.     if (*fullPath) {
  625.     *flag = YES;
  626.     } else {
  627.     *fullPath = "";
  628.     *flag = NO;
  629.     }
  630.     return 0;
  631. }
  632.  
  633. - (int)msgVersion:(const char **)aString ok:(int *)flag
  634. {
  635.     char buf[20];
  636.  
  637.     sprintf(buf, "3.0 (v%2d)", DrawVersion);
  638.     *aString = NXCopyStringBuffer(buf);
  639.     *flag = YES;
  640.  
  641.     return 0;
  642. }
  643.  
  644. - (int)msgFile:(const char **)fullPath ok:(int *)flag
  645. {
  646.     const char *file;
  647.  
  648.     file = [[self currentDocument] filename];
  649.     if (file) {
  650.     *fullPath = file;
  651.     *flag = YES;
  652.     } else {
  653.     *fullPath = "";
  654.     *flag = NO;
  655.     }
  656.  
  657.     return 0;
  658. }
  659.  
  660. - (BOOL)shouldRunPrintPanel:sender
  661. /*
  662.  * When NXApp prints, don't bring up the PrintPanel.
  663.  */
  664. {
  665.     return NO;
  666. }
  667.  
  668. - (int)msgPrint:(const char *)fullPath ok:(int *)flag
  669. {
  670.     BOOL close;
  671.     id document = nil;
  672.  
  673.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  674.     InMsgPrint = YES;
  675.     if (document = [findDocument(fullPath) delegate]) {
  676.         close = NO;
  677.     } else {
  678.         document = openDocument(fullPath, NO);
  679.         if (document) haveOpenedDocument = YES;
  680.         close = YES;
  681.     }
  682.     if (document && ![[document view] isEmpty]) {
  683.         [NXApp setPrintInfo:[document printInfo]];
  684.         [[document view] printPSCode:self];
  685.         if (close) [[[document view] window] close];
  686.         *flag = YES;
  687.     } else {
  688.         *flag = NO;
  689.     }
  690.     InMsgPrint = NO;
  691.     } else {
  692.     *flag = NO;
  693.     }
  694.  
  695.     return 0;
  696. }
  697.  
  698. - (int)msgSelection:(const char **)bytes length:(int *)len asType:(const char *)aType ok:(int *)flag
  699. {
  700.     int maxlen;
  701.     NXStream *stream;
  702.  
  703.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  704.     if (!strcmp(aType, DrawPboardType)) {
  705.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  706.         if ([[[self currentDocument] view] copySelectionToStream:stream]) {
  707.         NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
  708.         *flag = YES;
  709.         } else {
  710.         *flag = NO;
  711.         }
  712.         NXClose(stream);
  713.     } else if (!strcmp(aType, NXPostScriptPboardType)) {
  714.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  715.         if ([[[self currentDocument] view] copySelectionAsPSToStream:stream]) {
  716.         NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
  717.         *flag = YES;
  718.         } else {
  719.         *flag = NO;
  720.         }
  721.         NXClose(stream);
  722.     } else if (!strcmp(aType, NXTIFFPboardType)) {
  723.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  724.         if ([[[self currentDocument] view] copySelectionAsTIFFToStream:stream]) {
  725.         NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
  726.         *flag = YES;
  727.         } else {
  728.         *flag = NO;
  729.         }
  730.         NXClose(stream);
  731.     } else {
  732.         *flag = NO;
  733.     }
  734.     } else {
  735.     *flag = NO;
  736.     }
  737.     
  738.     if (!*flag) {
  739.     *bytes = "";
  740.     *len = 0;
  741.     }
  742.  
  743.     return 0;
  744. }
  745.  
  746. - (int)msgCopyAsType:(const char *)aType ok:(int *)flag
  747. {
  748.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  749.     if (!strcmp(aType, NXPostScriptPboardType) ||
  750.         !strcmp(aType, NXTIFFPboardType) ||
  751.         !strcmp(aType, DrawPboardType)) {
  752.         *flag = ([[[self currentDocument] view] copy:self] ? YES : NO);
  753.     } else {
  754.         *flag = NO;
  755.     }
  756.     } else {
  757.     *flag = NO;
  758.     }
  759.     return 0;
  760. }
  761.  
  762. - (int)msgCutAsType:(const char *)aType ok:(int *)flag
  763. {
  764.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  765.     if (!strcmp(aType, NXPostScriptPboardType) ||
  766.         !strcmp(aType, NXTIFFPboardType) ||
  767.         !strcmp(aType, DrawPboardType)) {
  768.         *flag = ([[[self currentDocument] view] cut:self] ? YES : NO);
  769.     } else {
  770.         *flag = NO;
  771.     }
  772.     } else {
  773.     *flag = NO;
  774.     }
  775.     return 0;
  776. }
  777.  
  778. - (int)msgPaste:(int *)flag;
  779. {
  780.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  781.     *flag = ([[[self currentDocument] view] paste:self] ? YES : NO);
  782.     } else {
  783.     *flag = NO;
  784.     }
  785.     return 0;
  786. }
  787.  
  788. - (int)msgQuit:(int *)flag
  789. {
  790.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  791.     *flag = ([self terminate:self cancellable:YES] ? NO : YES);
  792.     if (*flag) [NXApp perform:@selector(terminate:) with:nil afterDelay:1 cancelPrevious:YES];
  793.     } else {
  794.     *flag = NO;
  795.     }
  796.     return 0;
  797. }
  798.  
  799. /* Global cursor setting */
  800.  
  801. - cursor
  802. /*
  803.  * This is called by DrawDocument objects who want to set the cursor
  804.  * depending on what the currently selected tool is (as well as on whether
  805.  * the Control key has been pressed indicating that the select tool is
  806.  * temporarily set--see sendEvent:).
  807.  */
  808. {
  809.     id theCursor = nil;
  810.     if (!cursorPushed) theCursor = [[self currentGraphic] cursor];
  811.     return theCursor ? theCursor : NXArrow;
  812. }
  813.  
  814. - sendEvent:(NXEvent *)event
  815. /*
  816.  * We override this because we need to find out when the control key is down
  817.  * so we can set the arrow cursor so the user knows she is (temporarily) in
  818.  * select mode.
  819.  */
  820. {
  821.     if (event && event->type < NX_KITDEFINED) {    /* mouse or keyboard event */
  822.     if (event->flags & NX_CONTROLMASK) {
  823.         if (!cursorPushed && currentGraphic) {
  824.         cursorPushed = YES;
  825.         [[self currentDocument] resetCursor];
  826.         }
  827.     } else if (cursorPushed) {
  828.         cursorPushed = NO;
  829.         [[self currentDocument] resetCursor];
  830.     }
  831.     }
  832.  
  833.     return [super sendEvent:event];
  834. }
  835.  
  836. /* Automatic update methods */
  837.  
  838. - (BOOL)menuItemUpdate:(MenuCell *)menuCell
  839. /*
  840.  * Method called by all menu items which send their actions to the
  841.  * First Responder.  First, if the object which would respond were the
  842.  * action sent down the responder chain also responds to the message
  843.  * validateCommand:, then it is sent validateCommand: to determine
  844.  * whether that command is valid now, otherwise, if there is a responder
  845.  * to the message, then it is assumed that the item is valid.
  846.  * The method returns YES if the cell has changed its appearance (so that
  847.  * the caller (a Menu) knows to redraw it).
  848.  */
  849. {
  850.     SEL action;
  851.     id responder, target;
  852.     BOOL enable;
  853.  
  854.     target = [menuCell target];
  855.     enable = [menuCell isEnabled];
  856.  
  857.     if (!target) {
  858.     action = [menuCell action];
  859.     responder = [self calcTargetForAction:action];
  860.     if ([responder respondsTo:@selector(validateCommand:)]) {
  861.         enable = [responder validateCommand:menuCell];
  862.     } else {
  863.         enable = responder ? YES : NO;
  864.     }
  865.     }
  866.  
  867.     if ([menuCell isEnabled] != enable) {
  868.     [menuCell setEnabled:enable];
  869.     return YES;
  870.     } else {
  871.     return NO;
  872.     }
  873. }
  874.  
  875. - (BOOL)validateCommand:(MenuCell *)menuCell
  876. /*
  877.  * The only command DrawApp itself controls is saveAll:.
  878.  * Save All is enabled only if there are any documents open.
  879.  */
  880. {
  881.     SEL action = [menuCell action];
  882.  
  883.     if (action == @selector(saveAll:)) {
  884.     return findDocument(NULL) ? YES : NO;
  885.     }
  886.  
  887.     return YES;
  888. }
  889.  
  890. /*
  891.  * This is a very funky method and tricks of this sort are not generally
  892.  * recommended, but this hack is done so that new Graphic subclasses can
  893.  * be added to the program without changing even one line of code (except,
  894.  * of course, to implement the subclass itself).
  895.  *
  896.  * The objective-C method objc_lookUpClass() is used to find the factory object
  897.  * corresponding to the name of the icon of the cell sending the setCurrentGraphic:
  898.  * message.
  899.  *
  900.  * Again, this is not recommended procedure, but it illustrates how
  901.  * objective-C can be used to make some funky runtime dependent decisions.
  902.  */
  903.  
  904. - setCurrentGraphic:sender
  905. /*
  906.  * The sender's icon is queried.  If that name corresponds to the name
  907.  * of a class, then that class is set as the currentGraphic.  If not,
  908.  * then the select tool is put into effect.
  909.  */
  910. {
  911.     id cell;
  912.     const char *className;
  913.  
  914.     if (cell = [sender selectedCell]) {
  915.     if (className = [cell icon]) {
  916.         currentGraphic = objc_lookUpClass(className);
  917.     } else {
  918.         currentGraphic = nil;
  919.     }
  920.     if (!currentGraphic) [tools selectCellAt:0 :0];
  921.     [[self currentDocument] resetCursor];
  922.     }
  923.  
  924.     return self;
  925. }
  926.  
  927. @end
  928.